package org.abc.fund.service.trade;

import org.abc.fund.service.trade.request.CorrectionRequest;
import jakarta.persistence.EntityNotFoundException;
import org.abc.fund.entity.combination.CombinationProduct;
import org.abc.fund.entity.funds.FundBasicInfo;
import org.abc.fund.entity.trade.*;
import org.abc.fund.entity.user.User;
import org.abc.fund.repository.combination.CombinationProductRepository;
import org.abc.fund.repository.funds.FundBasicInfoRepository;
import org.abc.fund.repository.trade.AccountRepository;
import org.abc.fund.repository.trade.SettlementRepository;
import org.abc.fund.repository.trade.TradeOrderRepository;
import org.abc.fund.repository.user.UserRepository;
import org.abc.fund.service.trade.request.CreateTradeOrderRequest;
import org.abc.fund.service.trade.request.SettleOrderRequest;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

@Service
public class TradeService {

  private final TradeOrderRepository tradeOrderRepository;
  private final AccountRepository accountRepository;
  private final UserRepository userRepository;
  private final CombinationProductRepository combinationProductRepository;
  private final FundBasicInfoRepository fundBasicInfoRepository;
  private final SettlementRepository settlementRepository;

  public TradeService(
      TradeOrderRepository tradeOrderRepository,
      AccountRepository accountRepository,
      UserRepository userRepository,
      CombinationProductRepository combinationProductRepository,
      FundBasicInfoRepository fundBasicInfoRepository,
      SettlementRepository settlementRepository) {
    this.tradeOrderRepository = tradeOrderRepository;
    this.accountRepository = accountRepository;
    this.userRepository = userRepository;
    this.combinationProductRepository = combinationProductRepository;
    this.fundBasicInfoRepository = fundBasicInfoRepository;
    this.settlementRepository = settlementRepository;
  }

  @Transactional
  public TradeOrder createTradeOrder(CreateTradeOrderRequest request) {
    User user =
        userRepository
            .findById(request.getUserId())
            .orElseThrow(() -> new IllegalArgumentException("User not found"));
    Account account =
        accountRepository
            .findById(request.getAccountId())
            .orElseThrow(() -> new IllegalArgumentException("Account not found"));

    if (request.getType() == TradeOrder.TradeType.BUY
        && account.getBalance().compareTo(request.getAmount()) < 0) {
      throw new IllegalStateException("Insufficient balance");
    }

    CombinationProduct product =
        combinationProductRepository
            .findById(request.getProductId())
            .orElseThrow(() -> new IllegalArgumentException("Product not found"));

    TradeOrder order =
        TradeOrder.builder()
            .user(user)
            .account(account)
            .type(request.getType())
            .status(TradeOrder.TradeStatus.PENDING)
            .amount(request.getAmount())
            .build();

    List<TradeOrderDetail> details = new ArrayList<>();
    for (CreateTradeOrderRequest.Detail detailRequest : request.getDetails()) {
      FundBasicInfo fund =
          fundBasicInfoRepository
              .findById(detailRequest.getFundCode())
              .orElseThrow(() -> new IllegalArgumentException("Fund not found"));
      TradeOrderDetail orderDetail =
          TradeOrderDetail.builder()
              .tradeOrder(order)
              .fund(fund)
              .amount(detailRequest.getAmount())
              .shares(null)
              .direction(detailRequest.getDirection())
              .build();
      details.add(orderDetail);
    }
    order.setDetails(details);

    return tradeOrderRepository.save(order);
  }

  @Transactional
  public TradeOrder approveTradeOrder(Long orderId, Long approverId) {
    TradeOrder order =
        tradeOrderRepository
            .findById(orderId)
            .orElseThrow(() -> new IllegalArgumentException("Order not found"));
    User approver =
        userRepository
            .findById(approverId)
            .orElseThrow(() -> new IllegalArgumentException("Approver not found"));

    if (order.getStatus() != TradeOrder.TradeStatus.PENDING) {
      throw new IllegalStateException("Only pending orders can be approved");
    }

    order.setStatus(TradeOrder.TradeStatus.APPROVED);
    order.setApprover(approver);
    order.setApprovedAt(LocalDateTime.now());

    // 交易执行只处理资金冻结/预留
    executeTrade(order);

    order.setStatus(TradeOrder.TradeStatus.COMPLETED);
    return tradeOrderRepository.save(order);
  }

  @Transactional
  public TradeOrder rejectTradeOrder(Long orderId, Long approverId, String reason) {
    TradeOrder order =
        tradeOrderRepository
            .findById(orderId)
            .orElseThrow(() -> new IllegalArgumentException("Order not found"));
    User approver =
        userRepository
            .findById(approverId)
            .orElseThrow(() -> new IllegalArgumentException("Approver not found"));

    if (order.getStatus() != TradeOrder.TradeStatus.PENDING) {
      throw new IllegalStateException("Only pending orders can be rejected");
    }

    order.setStatus(TradeOrder.TradeStatus.REJECTED);
    order.setApprover(approver);
    order.setApprovedAt(LocalDateTime.now());
    order.setRejectionReason(reason);
    return tradeOrderRepository.save(order);
  }

  @Transactional(readOnly = true)
  public Optional<TradeOrder> getTradeOrderDetails(Long orderId) {
    return tradeOrderRepository.findById(orderId);
  }

  private void executeTrade(TradeOrder order) {
    Account account =
        accountRepository
            .findById(order.getAccount().getUserId())
            .orElseThrow(
                () -> new EntityNotFoundException("Account not found during trade execution."));

    if (order.getType() == TradeOrder.TradeType.BUY) {
      // 仅扣减资金，不操作持仓
      account.setBalance(account.getBalance().subtract(order.getAmount()));
    } else if (order.getType() == TradeOrder.TradeType.SELL) {
      // 卖出时，资金在交割后才到账，此处可选择不操作或做预增（取决于业务模型）
      // 为简化，我们假设资金在交割后才更新
    }

    accountRepository.save(account);
  }

  private void updateHoldingOnSettle(
      Account account,
      String fundCode,
      BigDecimal settledShares,
      TradeOrderDetail.Direction direction) {
    BigDecimal sharesChange =
        (direction == TradeOrderDetail.Direction.BUY) ? settledShares : settledShares.negate();

    Optional<Holding> existingHolding =
        account.getHoldings().stream().filter(h -> h.getFundCode().equals(fundCode)).findFirst();

    if (existingHolding.isPresent()) {
      Holding holding = existingHolding.get();
      BigDecimal newShares = holding.getShares().add(sharesChange);
      if (newShares.compareTo(BigDecimal.ZERO) <= 0) {
        account.getHoldings().remove(holding);
      } else {
        holding.setShares(newShares);
      }
    } else if (sharesChange.compareTo(BigDecimal.ZERO) > 0) {
      Holding newHolding = Holding.builder().fundCode(fundCode).shares(sharesChange).build();
      account.getHoldings().add(newHolding);
    }
  }

  @Transactional
  public TradeOrder settleOrder(Long orderId, SettleOrderRequest request) {
    TradeOrder order =
        tradeOrderRepository
            .findById(orderId)
            .orElseThrow(() -> new EntityNotFoundException("Order not found: " + orderId));

    if (order.getStatus() != TradeOrder.TradeStatus.COMPLETED) {
      throw new IllegalStateException("Only COMPLETED orders can be settled.");
    }

    Account account =
        accountRepository
            .findById(order.getAccount().getUserId())
            .orElseThrow(() -> new EntityNotFoundException("Account not found for settlement."));

    boolean hasFailures = false;
    for (SettleOrderRequest.SettleDetail detailDto : request.getDetails()) {
      TradeOrderDetail orderDetail =
          order.getDetails().stream()
              .filter(od -> od.getId().equals(detailDto.getOrderDetailId()))
              .findFirst()
              .orElseThrow(
                  () ->
                      new EntityNotFoundException(
                          "OrderDetail not found: " + detailDto.getOrderDetailId()));

      if (detailDto.isSuccess()) {
        orderDetail.setStatus(TradeOrder.TradeStatus.SETTLED);
        updateHoldingOnSettle(
            account,
            orderDetail.getFund().getCode(),
            detailDto.getSettledShares(),
            orderDetail.getDirection());

        Settlement settlement =
            Settlement.builder()
                .tradeOrderDetail(orderDetail)
                .settledAmount(detailDto.getSettledAmount())
                .settledShares(detailDto.getSettledShares())
                .fee(detailDto.getFee())
                .build();
        settlementRepository.save(settlement);
      } else {
        hasFailures = true;
        orderDetail.setStatus(TradeOrder.TradeStatus.FAILED);
        orderDetail.setFailureReason(detailDto.getFailureReason());
      }
    }

    // 根据是否有失败明细更新主订单状态
    if (hasFailures) {
      boolean allFailed =
          order.getDetails().stream().allMatch(d -> d.getStatus() == TradeOrder.TradeStatus.FAILED);
      order.setStatus(
          allFailed ? TradeOrder.TradeStatus.FAILED : TradeOrder.TradeStatus.PARTIALLY_FAILED);
    } else {
      order.setStatus(TradeOrder.TradeStatus.SETTLED);
    }

    accountRepository.save(account);
    return tradeOrderRepository.save(order);
  }

  @Transactional(readOnly = true)
  public List<TradeOrder> findFailedOrders() {
    return tradeOrderRepository.findOrdersWithFailures();
  }

  @Transactional
  public TradeOrder correctFailedTrade(CorrectionRequest request) {
    TradeOrder originalOrder =
        tradeOrderRepository
            .findById(request.getFailedOrderDetailId())
            .orElseThrow(
                () -> new EntityNotFoundException("Original failed order detail not found."));

    TradeOrderDetail failedDetail =
        originalOrder.getDetails().stream()
            .filter(d -> d.getId().equals(request.getFailedOrderDetailId()))
            .findFirst()
            .orElseThrow(
                () -> new EntityNotFoundException("Original failed order detail not found."));

    if (failedDetail.getStatus() != TradeOrder.TradeStatus.FAILED) {
      throw new IllegalStateException("Only FAILED order details can be corrected.");
    }

    User approver =
        userRepository
            .findById(request.getApproverId())
            .orElseThrow(() -> new IllegalArgumentException("Approver not found"));
    FundBasicInfo newFund =
        fundBasicInfoRepository
            .findById(request.getNewFundCode())
            .orElseThrow(() -> new IllegalArgumentException("New fund not found"));

    // 创建一个新的修正订单
    TradeOrder correctionOrder =
        TradeOrder.builder()
            .user(originalOrder.getUser())
            .account(originalOrder.getAccount())
            .type(TradeOrder.TradeType.REBALANCE) // 修正交易可视为一种特殊的调仓
            .status(TradeOrder.TradeStatus.PENDING) // 新订单进入待审核状态
            .amount(request.getAmount())
            .build();

    TradeOrderDetail correctionDetail =
        TradeOrderDetail.builder()
            .tradeOrder(correctionOrder)
            .fund(newFund)
            .amount(request.getAmount())
            .direction(failedDetail.getDirection()) // 保持原方向
            .correctedFromOrderDetailId(failedDetail.getId()) // 建立关联
            .build();

    correctionOrder.setDetails(List.of(correctionDetail));

    return tradeOrderRepository.save(correctionOrder);
  }

  public List<TradeOrder> findPendingOrders() {
    return tradeOrderRepository.findByStatus(TradeOrder.TradeStatus.PENDING);
  }

  @Transactional(readOnly = true)
  public Page<TradeOrder> getOrdersByUserIdAndStatus(
      Long userId, String status, Pageable pageable) {
    if (status != null && !status.isEmpty()) {
      try {
        TradeOrder.TradeStatus tradeStatus = TradeOrder.TradeStatus.valueOf(status.toUpperCase());
        return tradeOrderRepository.findByUser_IdAndStatus(userId, tradeStatus, pageable);
      } catch (IllegalArgumentException e) {
        // Handle invalid status string
        return Page.empty(pageable);
      }
    } else {
      return tradeOrderRepository.findByUser_Id(userId, pageable);
    }
  }
}
